package org.scratchcrew.web.cometd;

import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletContext;

import org.cometd.Client;
import org.cometd.DataFilter;
import org.cometd.server.ClientImpl;
import org.cometd.server.continuation.ContinuationBayeux;
import org.cometd.server.continuation.ContinuationCometdServlet;
import org.cometd.server.filter.JSONDataFilter;
import org.eclipse.jetty.util.ajax.JSON;
import org.scratchcrew.services.security.SecurityContext;
import org.scratchcrew.services.security.SecurityContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.ServletContextAware;

/**
 * A subclass of {@link ContinuationBayeux} that allows full initialization of
 * bayeux outside the {@link ContinuationCometdServlet}.
 * <p>
 * Copied from
 * <code>org.springbyexample.cometd.continuation.SpringContinuationBayeux</code>
 * </p>
 * 
 * @author vtsanev
 * @author spring by example authors
 */
public class SpringContinuationBayeux extends ContinuationBayeux implements
		ServletContextAware {

	protected ServletContext servletContext = null;

	protected String filters = null;
	protected long maxInterval = 10000;
	protected final Logger logger = LoggerFactory
			.getLogger(SpringContinuationBayeux.class);
	
	protected final Map<String, String> clientIdToUsernameMap = new HashMap<String, String>();

	/**
	 * Implementation of <code>ServletContextAware</code>.
	 */
	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

	/**
	 * Gets filters as JSON objects.
	 */
	public String getFilters() {
		return filters;
	}

	/**
	 * Sets filters as JSON objects.
	 */
	public void setFilters(String filters) {
		this.filters = filters;
	}

	/**
	 * <p>
	 * Sets max interval.
	 * </p>
	 * 
	 * <p>
	 * <strong>Note</strong>: Overriding parent because it sets value on
	 * internal instance before initialization.
	 * </p>
	 */
	@Override
	public void setMaxInterval(long maxInterval) {
		this.maxInterval = maxInterval;
	}

	@Override
	public ClientImpl newRemoteClient() {
		final ClientImpl result = super.newRemoteClient();
		SecurityContext currentContext = SecurityContextHolder
				.getCurrentContext();
		if (currentContext != null) {
			logger.info(String.format("Client with id %s created for user %s.",
					result.getId(), currentContext.getUserName()));
			clientIdToUsernameMap.put(result.getId(), currentContext.getUserName());
		} else {
			logger.info(String.format("annoymous client with id %s  created",
					result.getId()));
		}

		return result;
	}

	@Override
	public Client removeClient(final String clientId) {
		clientIdToUsernameMap.remove(clientId);
		return super.removeClient(clientId);
	}
	
	public String getUserNameByClientId(final String clientId) {
		return clientIdToUsernameMap.get(clientId);
	}
	
	/**
	 * Initializes bayeux.
	 */
	public void init() {
		this.initialize(servletContext);
		super.setMaxInterval(maxInterval);

		// based on AbstractCometdServlet.init()
		if (filters != null) {
			try {
				Object[] objects = (Object[]) JSON.parse(new StringReader(
						filters));

				for (int i = 0; objects != null && i < objects.length; i++) {
					Map<?, ?> filterDefinition = (Map<?, ?>) objects[i];

					String filterClassName = (String) filterDefinition
							.get("class");
					if (filterClassName != null) {
						logger.warn(filters + " file uses "
								+ "deprecated \"class\" name."
								+ " Use \"filter\" instead");
					} else {
						filterClassName = (String) filterDefinition
								.get("filter");
					}
					DataFilter filter = (DataFilter) ClassUtils.forName(
							filterClassName).newInstance();

					if (filter instanceof JSONDataFilter)
						((JSONDataFilter) filter).init(filterDefinition
								.get("init"));

					getChannel((String) filterDefinition.get("channels"), true)
							.addDataFilter(filter);
				}
			} catch (IOException e) {
				throw new IllegalArgumentException(
						"Unable to initialize bayuex filters.  "
								+ e.getMessage());
			} catch (InstantiationException e) {
				throw new IllegalArgumentException(
						"Unable to initialize bayuex filters.  "
								+ e.getMessage());
			} catch (IllegalAccessException e) {
				throw new IllegalArgumentException(
						"Unable to initialize bayuex filters.  "
								+ e.getMessage());
			} catch (ClassNotFoundException e) {
				throw new IllegalArgumentException(
						"Unable to initialize bayuex filters.  "
								+ e.getMessage());
			} catch (LinkageError e) {
				throw new IllegalArgumentException(
						"Unable to initialize bayuex filters.  "
								+ e.getMessage());
			}
		}
	}

}
